package cc.rome753.wat;

import android.content.Context;
import android.media.MediaRecorder;
import android.util.Log;

import org.webrtc.AudioSource;
import org.webrtc.AudioTrack;
import org.webrtc.Camera1Enumerator;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.SurfaceTextureHelper;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoCapturer;
import org.webrtc.VideoSource;
import org.webrtc.VideoTrack;
import org.webrtc.audio.JavaAudioDeviceModule;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class WebrtcClient {
    private static final String TAG = WebrtcClient.class.getSimpleName();
    private static final boolean DEBUG = true;

    /**
     * 表示 webrtc 是否已经初始化完成。
     */
    private boolean initFlag;

    /**
     * 表示 webrtc 是否已经启动；特指和远程端建立起连接，并可以发送接收数据。
     */
    private boolean startFlag;
    /**
     * 是否播放远程端发过来的视频数据
     */
    private boolean playVideoFlag;
    /**
     * 是否播放远程端发过来的音频数据
     */
    private boolean playAudioFlag;
    /**
     * 是否采集视频数据发送给远程端
     */
    private boolean recordVideoFlag;
    /**
     * 是否采集音频数据发送给远程端
     */
    private boolean recordAudioFlag;

    EglBase.Context eglBaseContext;
    PeerConnectionFactory peerConnectionFactory;

    MediaStream mediaStream;
    List<PeerConnection.IceServer> iceServers;

    HashMap<String, PeerConnection> peerConnectionMap;
    ArrayList<SurfaceViewRenderer> remoteViews = new ArrayList<>();
    ArrayList<MediaStream> remoteStreams = new ArrayList<>();
    int remoteViewsIndex = 0;
    SurfaceViewRenderer localView;
    VideoTrack localVideoTrack;
    VideoCapturer videoCapturer;
    VideoSource videoSource;
    AudioSource audioSource;
    AudioTrack audioTrack;
    SurfaceTextureHelper surfaceTextureHelper;


    private Context context;

    public WebrtcClient(Context context) {
        if (DEBUG) Log.i(TAG, "WebrtcClient()");
        this.context = context;

        peerConnectionMap = new HashMap<>();
        iceServers = new ArrayList<>();
        iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());

        eglBaseContext = EglBase.create().getEglBaseContext();

        // create PeerConnectionFactory
        PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions
                .builder(context)
                .createInitializationOptions());
        PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
        DefaultVideoEncoderFactory defaultVideoEncoderFactory =
                new DefaultVideoEncoderFactory(eglBaseContext, true, true);
        DefaultVideoDecoderFactory defaultVideoDecoderFactory =
                new DefaultVideoDecoderFactory(eglBaseContext);

        JavaAudioDeviceModule.Builder admbuilder =  JavaAudioDeviceModule.builder(context);
        admbuilder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);

        peerConnectionFactory = PeerConnectionFactory.builder()
                .setOptions(options)
                .setVideoEncoderFactory(defaultVideoEncoderFactory)
                .setVideoDecoderFactory(defaultVideoDecoderFactory)
                .setAudioDeviceModule(admbuilder.createAudioDeviceModule())
                .createPeerConnectionFactory();

        surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", eglBaseContext);
        // create VideoCapturer
        videoCapturer = createCameraCapturer(true);
        videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast());
        videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver());
        videoCapturer.startCapture(480, 640, 30);

        // create VideoTrack
        localVideoTrack = peerConnectionFactory.createVideoTrack("100", videoSource);

        mediaStream = peerConnectionFactory.createLocalMediaStream("mediaStream");
        mediaStream.addTrack(localVideoTrack);

        AudioSource audioSource =
                peerConnectionFactory.createAudioSource(new MediaConstraints());
        AudioTrack audioTrack = peerConnectionFactory.createAudioTrack(
                "101",
                audioSource);
        mediaStream.addTrack(audioTrack);

        initFlag = true;
    }

    public void release() {
        if (DEBUG) Log.i(TAG, "release()");
        if (videoCapturer != null) {
            videoCapturer.dispose();
            videoCapturer = null;
        }
        if (videoSource != null) {
            videoSource.dispose();
            videoSource = null;
        }
        if (audioSource != null) {
            audioSource.dispose();
            audioSource = null;
        }
        if (mediaStream != null) {
            mediaStream.removeTrack(localVideoTrack);
            mediaStream.removeTrack(audioTrack);
//            mediaStream.dispose();
            mediaStream = null;
        }
        if (localVideoTrack != null) {
            localVideoTrack.removeSink(localView);
            localVideoTrack.dispose();
            localVideoTrack = null;
        }
        if (audioTrack != null) {
            audioTrack.dispose();
            audioTrack = null;
        }
        Iterator<Map.Entry<String, PeerConnection>> iterator = peerConnectionMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, PeerConnection> entry = iterator.next();
            PeerConnection peerConnection = entry.getValue();
            if (peerConnection != null) {
                peerConnection.dispose();
                peerConnection = null;
            }
            iterator.remove();
        }
        if (peerConnectionFactory != null) {
            peerConnectionFactory.dispose();
            peerConnectionFactory = null;
        }
        if (surfaceTextureHelper != null) {
            surfaceTextureHelper.dispose();
            surfaceTextureHelper = null;
        }

        initFlag = false;
    }

    public boolean start() {
        if (DEBUG) Log.i(TAG, "start(), startFlag: " + startFlag);
        if (startFlag)
            return true;

        startFlag = true;
        return true;
    }

    public void stop() {
        if (DEBUG) Log.i(TAG, "stop(), startFlag: " + startFlag);
        if (!startFlag) {
            return;
        }
        startFlag = false;
    }

    public boolean isInitFlag() {
        if (DEBUG) Log.i(TAG, "isInitFlag(), initFlag: " + initFlag);
        return initFlag;
    }

    public boolean isStartFlag() {
        if (DEBUG) Log.i(TAG, "isStartFlag(), startFlag: " + startFlag);
        return startFlag;
    }

    public boolean isPlayVideoFlag() {
        if (DEBUG) Log.i(TAG, "isPlayVideoFlag(), playVideoFlag: " + playVideoFlag);
        return playVideoFlag;
    }

    public void setPlayVideoFlag(boolean playVideoFlag) {
        if (DEBUG) Log.i(TAG, "setPlayVideoFlag(), origin: " + this.playVideoFlag + ", new: " + playVideoFlag);
        this.playVideoFlag = playVideoFlag;
        synchronized (remoteStreams) {
            for (int i = 0; i < remoteStreams.size(); i++) {
                MediaStream remoteStream = remoteStreams.get(i);
                if (remoteStream == null)
                    continue;
                remoteStream.videoTracks.get(0).removeSink(remoteViews.get(i));
            }
        }
    }

    public boolean isPlayAudioFlag() {
        if (DEBUG) Log.i(TAG, "isPlayAudioFlag(), playAudioFlag: " + playAudioFlag);
        return playAudioFlag;
    }

    public void setPlayAudioFlag(boolean playAudioFlag) {
        if (DEBUG) Log.i(TAG, "setPlayAudioFlag(), origin: " + this.playAudioFlag + ", new: " + playAudioFlag);
        this.playAudioFlag = playAudioFlag;
        synchronized (peerConnectionMap) {
            for (PeerConnection peerConnection : peerConnectionMap.values()) {
                if (peerConnection == null)
                    continue;
                peerConnection.setAudioPlayout(recordAudioFlag);
            }
        }
    }

    public boolean isRecordVideoFlag() {
        if (DEBUG) Log.i(TAG, "isRecordVideoFlag(), recordVideoFlag: " + recordVideoFlag);
        return recordVideoFlag;
    }

    public void setRecordVideoFlag(boolean recordVideoFlag) {
        if (DEBUG) Log.i(TAG, "setRecordVideoFlag(), origin: " + this.recordVideoFlag + ", new: " + recordVideoFlag);
        this.recordVideoFlag = recordVideoFlag;
        if (recordVideoFlag) {
            videoCapturer.startCapture(480, 640, 30);
        } else {
            try {
                videoCapturer.stopCapture();
            } catch (InterruptedException e) {
                Log.e(TAG, "", e);
            }
        }
    }

    public boolean isRecordAudioFlag() {
        if (DEBUG) Log.i(TAG, "isRecordAudioFlag(), recordAudioFlag: " + recordAudioFlag);
        return recordAudioFlag;
    }

    public void setRecordAudioFlag(boolean recordAudioFlag) {
        if (DEBUG) Log.i(TAG, "setRecordAudioFlag(), origin: " + this.recordAudioFlag + ", new: " + recordAudioFlag);
        this.recordAudioFlag = recordAudioFlag;
        synchronized (peerConnectionMap) {
            for (PeerConnection peerConnection : peerConnectionMap.values()) {
                if (peerConnection == null)
                    continue;
                peerConnection.setAudioRecording(recordAudioFlag);
            }
        }
    }

    public void setLocalView(SurfaceViewRenderer localView) {
        this.localView = localView;
        localView.setMirror(true);
        localView.init(eglBaseContext, null);
        // display in localView
        localVideoTrack.addSink(localView);
    }

    public void addRemoteView(SurfaceViewRenderer remoteView) {
        remoteView.setMirror(false);
        remoteView.init(eglBaseContext, null);
        remoteViews.add(remoteView);
    }


    private VideoCapturer createCameraCapturer(boolean isFront) {
        Camera1Enumerator enumerator = new Camera1Enumerator(false);
        final String[] deviceNames = enumerator.getDeviceNames();

        // First, try to find front facing camera
        for (String deviceName : deviceNames) {
            if (isFront ? enumerator.isFrontFacing(deviceName) : enumerator.isBackFacing(deviceName)) {
                VideoCapturer videoCapturer = enumerator.createCapturer(deviceName, null);

                if (videoCapturer != null) {
                    return videoCapturer;
                }
            }
        }

        return null;
    }


    public synchronized PeerConnection getOrCreatePeerConnection(String socketId) {
        PeerConnection peerConnection = peerConnectionMap.get(socketId);
        if(peerConnection != null) {
            return peerConnection;
        }
        peerConnection = peerConnectionFactory.createPeerConnection(iceServers, new PeerConnectionAdapter("PC:" + socketId) {
            @Override
            public void onIceCandidate(IceCandidate iceCandidate) {
                super.onIceCandidate(iceCandidate);
                SignalingClient.get().sendIceCandidate(iceCandidate, socketId);
            }

            @Override
            public void onAddStream(MediaStream mediaStream) {
                super.onAddStream(mediaStream);
                VideoTrack remoteVideoTrack = mediaStream.videoTracks.get(0);
                remoteVideoTrack.addSink(remoteViews.get(remoteViewsIndex++));

                AudioTrack remoteAudioTrack = mediaStream.audioTracks.get(0);
                Log.i("windboat", "remoteAudioTrack enable: " + remoteAudioTrack.enabled());
            }
        });
        peerConnection.addStream(mediaStream);
        peerConnection.setAudioPlayout(playAudioFlag);
        peerConnection.setAudioRecording(recordVideoFlag);

        peerConnectionMap.put(socketId, peerConnection);
        return peerConnection;
    }

}
